<?php
/**
 * 支付处理
 */

namespace Kcdns\Service\Pay;

class Pay
{

    protected $config;

    protected $info;

    protected $apidomain = '';

    protected $gateway = '';

    protected $driver = null;

    protected $payType = "";

    // 未支付
    const PAYMENT_STATUS_UNPAY = 'unpay';
    // 已经支付
    const PAYMENT_STATUS_PAY = 'pay';
    // 异常
    const PAYMENT_STATUS_ERROR = 'error';

    //回调执行状态
    const CALLBACK_STATUS_NONE = 'none';//未执行

    const CALLBACK_STATUS_DONE = 'done';//已执行

    const CALLBACK_STSTUS_ERROR = 'error';//执行异常

    //支付记录类型
    const PAYMENT_RECORD_TYPE_REFUND = 'refund';//退款记录类型

    const PAYMENT_RECORD_TYPE_PAY = 'pay';//支付记录类型

    //退款状态
    const PAYMENT_REFUND_STATUS_NONE = 'none';//未执行

    const PAYMENT_REFUND_STATUS_DONE = 'refund';//退款成功

    const PAYMENT_REFUND_STATUS_ERROR = 'error';//退款失败

    protected $statusLabel = array(
        self::PAYMENT_STATUS_UNPAY => '未支付',
        self::PAYMENT_STATUS_ERROR => '支付异常',
        self::PAYMENT_STATUS_PAY => '已支付'
    );

    protected $refundStatusLabel = array(
        self::PAYMENT_REFUND_STATUS_NONE => '未退款',
        self::PAYMENT_REFUND_STATUS_DONE => '已退款',
        self::PAYMENT_REFUND_STATUS_ERROR => '退款异常'
    );

    static $instance = null;

    static public function getInstance()
    {
        return is_null(self::$instance) ? (self::$instance = new self()) : self::$instance;
    }

    private function __construct()
    {
        //初始化
    }

    //支付调用入口
    public function __call($name, $arguments)
    {
        try {
            if (method_exists($this, $name)) {
                return call_user_func_array(array($this, $name), $arguments);
            } else {
                throw new \Exception('invalid method:' . $name);
            }
        } catch (\Exception $e) {
            //支付异常日志
            \KCSLog::WARN($e->getMessage());
            \KCSLog::DEBUG($e);
            return false;
        }
    }

    //当前支付驱动类型是否可用
    protected function isEnabled($payType)
    {
        try {
            $this->_loadConfig($payType);
            return true;
        } catch (\Exception $e) {
            return false;
        }
    }

    //支付发起
    protected function pay($params)
    {
        //默认参数初始化
        $params['returnUrl'] = $params['returnUrl'] ?: 'Pay/Gateway/callback';
        $params['notifyUrl'] = $params['notifyUrl'] ?: 'Pay/Gateway/notify';
        $params['orderDesc'] = $params['orderDesc'] ?: $params['orderTitle'];
        //加载支付驱动
        $this->_loadDriver($params['payType'], $params['extra']);
        //支付校验
        if ($this->driver->check()) {
            //生成支付记录
            $paymentInfo = $this->_creaetPayment($params['orderNo'], $params['amount'], $params['callback'], $params['redirectUrl'], $params['orderTitle'], $params['orderDesc'], $params['returnUrl'], $params['notifyUrl']);

            //构造支付表单
            $payForm = $this->driver->buildRequestForm($paymentInfo);
            //存储生成的表单
            $this->_savePayForm($paymentInfo['payment_id'], $payForm);
            return $payForm;
        }
        throw new \Exception('支付发起参数校验失败');
    }

    //支付发起
    protected function payMargin($params)
    {
        //默认参数初始化
        $params['returnUrl'] = $params['returnUrl'] ?: 'Pay/Gateway/callback';
        $params['notifyUrl'] = $params['notifyUrl'] ?: 'Pay/Gateway/notify';
        $params['orderDesc'] = $params['orderDesc'] ?: $params['orderTitle'];
        //加载支付驱动
        $this->_loadDriver($params['payType'], $params['extra']);
        //支付校验
        if ($this->driver->check()) {
            //生成支付记录
            $paymentInfo = $this->_creaetPaymentMargin($params['orderNo'], $params['amount'], $params['callback'], $params['redirectUrl'], $params['orderTitle'], $params['orderDesc'], $params['returnUrl'], $params['notifyUrl']);

            //构造支付表单
            $payForm = $this->driver->buildRequestForm($paymentInfo);
            //存储生成的表单
            $this->_savePayForm($paymentInfo['payment_id'], $payForm);
            return $payForm;
        }
        throw new \Exception('支付发起参数校验失败');
    }

    /**
     * 支付回调通知处理
     * @param $paySn 支付流水SN
     * @param $type  回调类型 notify:异步  return:同步
     */
    protected function notify($paySn, $type = 'notify')
    {
        //获取支付信息
        $paymentInfo = $this->_getPaymentInfo($paySn);
        if (!$paymentInfo) {
            throw new \Exception('无效的支付记录');
        }
        //加载支付驱动
        $this->_loadDriver($paymentInfo['pay_type']);
        //解析通知数据
        $notifyInfo = $this->driver->getRequest($type);
        unset($notifyInfo['paysn']);

        //\KCSLog::APP('PAY_RECORD',true,var_export($notifyInfo,true));
        //数据验签
        if ($this->driver->verifyNotify($notifyInfo) == false) {

            if ($paymentInfo['pay_type'] == 'aliwap' && $type == 'return' && $paymentInfo['status'] == self::PAYMENT_STATUS_PAY) {
                return $paymentInfo;
            }

            throw new \Exception('数据验证失败');
        }
        //已支付订单重复通知
        if ($paymentInfo['status'] == self::PAYMENT_STATUS_PAY) {
            //补充记录通知数据
            $this->_saveNotifyData($paymentInfo['id'], $notifyInfo, $type);
            return $type == 'notify' ? $this->driver->notifySuccess() : $paymentInfo;
        }
        //只有未支付状态的订单可执行支付回调流程
        if ($paymentInfo['status'] != self::PAYMENT_STATUS_UNPAY) {
            throw new \Exception('订单状态异常:' . $this->statusLabel[$paymentInfo['status']]);
        }

        //记录通知数据
        $this->_saveNotifyData($paymentInfo['id'], $notifyInfo, $type);

        //同步通知 不处理业务回调 直接返回
        if ($type == 'return') {
            return $paymentInfo;
        }
        //临时更改支付状态传递给业务回调
        $paymentInfo['status'] == self::PAYMENT_STATUS_PAY;
        //设置支付状态
        $this->_setPayStatus($paymentInfo['id'], self::PAYMENT_STATUS_PAY, $this->driver->getOuterNo($notifyInfo));
        if ($this->_callback($paymentInfo['callback'], $paymentInfo) !== true) {
            //业务回调异常
            $this->_setCallbackStatus($paymentInfo['id'], self::CALLBACK_STSTUS_ERROR);
            throw new \Exception('支付成功回调处理失败.' . var_export($notifyInfo, true));
        } else {
            $this->_setCallbackStatus($paymentInfo['id'], self::CALLBACK_STATUS_DONE);
            if ($type == 'notify') {
                //异步通知
                return $this->driver->notifySuccess();
            } else {
                //同步通知
                return $paymentInfo;
            }
        }

    }


    //发起退款
    protected function refund($orderNo, $amount, $callback)
    {
        //退款申请
        $returnUrl = 'Pay/Gateway/rcallback';//由服务器主动发起请求 并无同步通知返回 而是接口调用完毕 直接处理退款回调业务逻辑
        $notifyUrl = 'Pay/Gateway/rnotify';//部分第三方平台 退款支持异步通知处理
        //退款验证 获取退款记录
        $payment = $this->_checkRefund($orderNo, $amount);
        //退款记录存储
        $refundRecord = $this->_createRefund($payment, $amount, $returnUrl, $notifyUrl, $callback);
        //加载驱动
        $this->_loadDriver($payment['pay_type']);

        //发起退款
        $refundResponse = $this->driver->refund($payment['outer_sn'], $amount, $refundRecord, $payment);

        //退款结果与订单验证 可以不处理 退款发起成功 则数据是合法的请求

        //退款成功 设置退款状态  执行退款业务处理同步回调
        $this->_setPayStatus($refundRecord['payment_id'], self::PAYMENT_REFUND_STATUS_DONE);

        return $this->_handleCallback($refundRecord, 'return');
    }

    //支付单查询
    protected function query($paySn)
    {
        $paymentInfo = $this->_getPaymentInfo($paySn);
        //加载驱动
        $this->_loadDriver($paymentInfo['pay_type']);
        //查询订单
        return $this->driver->queryOrder($paymentInfo);
    }

    /**
     * 退款回调通知处理 为其他支付方式统一标准拓展 目前微信 支付宝暂未接入异步退款通知处理
     * @param $callback
     * @param $paymentInfo
     * @return mixed|string
     */
    protected function rnotify($paySn, $type = 'rreturn')
    {
        //获取支付信息
        $paymentInfo = $this->_getPaymentInfo($paySn);
        if (!$paymentInfo) {
            throw new \Exception('无效的支付记录');
        }
        //加载支付驱动
        $this->_loadDriver($paymentInfo['pay_type']);
        //解析通知数据
        $notifyInfo = $this->driver->getRequest($type);

        unset($notifyInfo['paysn']);

        //退款异步通知数据验证
        if ($this->driver->verifyRNotify($notifyInfo) == false) {
            throw new \Exception('数据验证失败');
        }
        //已退款订单重复通知 直接返回成功
        if ($paymentInfo['status'] == self::PAYMENT_REFUND_STATUS_DONE) {
            //补充记录通知数据
            $this->_saveNotifyData($paymentInfo['id'], $notifyInfo, $type);
            return $type == 'rnotify' ? $this->driver->notifySuccess() : $paymentInfo;
        }
        //只有发起退款状态的订单可执行退款回调流程
        if ($paymentInfo['status'] != self::PAYMENT_REFUND_STATUS_NONE) {
            throw new \Exception('订单状态异常:' . $this->refundStatusLabel[$paymentInfo['status']]);
        }
        //记录通知数据
        $this->_saveNotifyData($paymentInfo['id'], $notifyInfo, $type);
        //临时更改退款状态传递给业务回调
        $paymentInfo['status'] == self::PAYMENT_REFUND_STATUS_DONE;
        //设置支付状态
        $this->_setPayStatus($paymentInfo['id'], self::PAYMENT_REFUND_STATUS_DONE);
        //回调处理
        return $this->_handleCallback($paymentInfo, $type);
    }

    /**
     * @param $params
     * @param $payType
     * 开发用签名验证测试
     */
    protected function checkSign($params, $payType)
    {
        $this->_loadDriver($payType);
        return $this->driver->verifyNotify($params);
    }

    /**
     *支付、退款后续回调业务处理
     * @param array $paymentInfo 支付/退款记录
     * @param string $callbackType 回调类型 return notify  同步异步通知回调
     */
    private function _handleCallback($paymentInfo = array(), $callbackType = 'notify')
    {
        if ($this->_callback($paymentInfo['callback'], $paymentInfo) !== true) {
            //业务回调异常
            $this->_setCallbackStatus($paymentInfo['id'] ?: $paymentInfo['payment_id'], self::CALLBACK_STSTUS_ERROR);
            throw new \Exception('支付成功回调处理失败');
        } else {
            $this->_setCallbackStatus($paymentInfo['id'] ?: $paymentInfo['payment_id'], self::CALLBACK_STATUS_DONE);
            if ($callbackType == 'notify') {
                //异步通知
                return $this->driver->notifySuccess();
            } else {
                //同步通知
                return $paymentInfo;
            }
        }
    }

    //执行业务回调
    private function _callback($callback, $paymentInfo)
    {
        if (is_callable($callback)) {
            try {
                return call_user_func($callback, $paymentInfo);
            } catch (\Exception $e) {
                return $e->getMessage();
            }
        }
        return "无效的支付回调:" . var_export($callback, true);
    }

    //设置支付流水状态 异步通知回来后 同时更新支付记录的外部支付订单号数据
    private function _setPayStatus($payId, $status, $outerSn = '')
    {
        $where = array(
            'id' => $payId,
        );
        $save = array('status' => $status, 'success_time' => date('Y-m-d H:i:s'));
        $outerSn && $save['outer_sn'] = $outerSn;
        return M('Payment')->where($where)->save($save);
    }

    //设置业务回调状态
    private function _setCallbackStatus($payId, $status)
    {
        $where = array(
            'id' => $payId,
        );
        return M('Payment')->where($where)->save(array('callback_status' => $status));
    }


    //生成支付/退款流水记录
    private function _creaetPayment($orderNo, $amount, $callback, $redirectUrl, $orderTitle, $orderDesc, $returnUrl, $notifyUrl)
    {
        $order = M('order')->where(['order_no' => $orderNo])->find();
        if (!$order) {
            throw new \Exception("无效的订单号");
        }
        if ($order['pay_status']) {
            throw new \Exception("该订单已支付");
        }

        $is_paid = M('payment')->where(['order_no' => $orderNo, 'status' => 'pay'])->find();
        if ($is_paid) {
            throw new \Exception("该订单已支付");
        }

        if ($amount * 1 < 0.01) {
            throw new \Exception('金额不能小于0.01！');
        }
        $data['order_no'] = $orderNo;
        $data['pay_type'] = strtolower($this->payType);
        $data['amount'] = $amount;
        $data['status'] = self::PAYMENT_STATUS_UNPAY;
        $data['callback_status'] = self::CALLBACK_STATUS_NONE;
        $data['callback'] = $callback ?: '\Service\Order\Service::payCallback';
        $data['create_time'] = date('Y-m-d H:i:s');
        $data['order_info'] = serialize(array('title' => $orderTitle, 'desc' => $orderDesc));
        $data['pay_sn'] = $this->_createPaySn($data);
        $data['return'] = $this->_createCallbackUrl($data['pay_sn'], $returnUrl);
        $data['notify'] = $this->_createCallbackUrl($data['pay_sn'], $notifyUrl);
        $data['redirect'] = $redirectUrl;
        $data['type'] = self::PAYMENT_RECORD_TYPE_PAY;
        if (!M('Payment')->add($data)) {
            throw new \Exception('支付记录创建失败！');
        }
        $paymentId = M('Payment')->getLastInsID();
        $data['payment_id'] = $paymentId;
        return $data;
    }

    //生成支付/退款流水记录
    private function _creaetPaymentMargin($orderNo, $amount, $callback, $redirectUrl, $orderTitle, $orderDesc, $returnUrl, $notifyUrl)
    {
        $order = M('margin_order')->where(['order_no' => $orderNo])->find();
        if (!$order) {
            throw new \Exception("无效的订单号");
        }
        if ($order['pay_status']) {
            throw new \Exception("该订单已支付");
        }

        $is_paid = M('payment')->where(['order_no' => $orderNo, 'status' => 'pay'])->find();
        if ($is_paid) {
            throw new \Exception("该订单已支付");
        }

        if ($amount * 1 < 0.01) {
            throw new \Exception('金额不能小于0.01！');
        }
        $data['order_no'] = $orderNo;
        $data['pay_type'] = strtolower($this->payType);
        $data['amount'] = $amount;
        $data['status'] = self::PAYMENT_STATUS_UNPAY;
        $data['callback_status'] = self::CALLBACK_STATUS_NONE;
        $data['callback'] = $callback ?: '\Service\Order\Service::payCallback';
        $data['create_time'] = date('Y-m-d H:i:s');
        $data['order_info'] = serialize(array('title' => $orderTitle, 'desc' => $orderDesc));
        $data['pay_sn'] = $this->_createPaySn($data);
        $data['return'] = $this->_createCallbackUrl($data['pay_sn'], $returnUrl);
        $data['notify'] = $this->_createCallbackUrl($data['pay_sn'], $notifyUrl);
        $data['redirect'] = $redirectUrl;
        $data['type'] = self::PAYMENT_RECORD_TYPE_PAY;
        if (!M('Payment')->add($data)) {
            throw new \Exception('支付记录创建失败！');
        }
        $paymentId = M('Payment')->getLastInsID();
        $data['payment_id'] = $paymentId;
        return $data;
    }

    //生成退款流水记录
    private function _createRefund($paymentRecord = array(), $refundAmount, $returnUrl, $notifyUrl, $callback)
    {
        if ($refundAmount * 1 < 0.01) {
            throw new \Exception('退款金额不能小于0.01！');
        }
        $data['order_no'] = $paymentRecord['order_no'];
        $data['pay_type'] = $paymentRecord['pay_type'];
        $data['amount'] = $refundAmount;
        $data['status'] = self::PAYMENT_REFUND_STATUS_NONE;
        $data['create_time'] = date('Y-m-d H:i:s');
        $data['pay_sn'] = $this->_createPaySn($paymentRecord);//本地退款流水号
        $data['type'] = self::PAYMENT_RECORD_TYPE_REFUND;
        $data['return'] = $this->_createCallbackUrl($data['pay_sn'], $returnUrl);
        $data['notify'] = $this->_createCallbackUrl($data['pay_sn'], $notifyUrl);
        $data['callback'] = $callback;//退款业务回调

        if (!M('Payment')->add($data)) {
            throw new \Exception('退款记录创建失败！');
        }
        $paymentId = M('Payment')->getLastInsID();
        $data['payment_id'] = $paymentId;
        return $data;
    }


    //加载支付驱动配置
    private function _loadConfig($payType)
    {
        $where = array(
            'type' => strtolower($payType),
            'status' => 1
        );
        $payConfig = M('PaymentSettings')->where($where)->find();
        if (!$payConfig) {
            throw new \Exception('无效的支付方式');
        }
        $payConfig['config'] = json_decode(kcone_think_decrypt($payConfig['config']), true);
        $config = array();
        foreach ($payConfig['config'] as $item) {
            $config[$item['key']] = $item['val'];
        }

        $config['mode'] = $payConfig['mode'];

        return $config;

//        $config= array(
//            'alipay'=>array(
//                // 收款账号邮箱
//                'email' => 'huanghe@kcdns.com',
//                // 加密key，开通支付宝账户后给予
//                'key' => 'a424ed4bd3a7d6aea720b86d4a360f75',
//                // 合作者ID，支付宝有该配置，开通易宝账户后给予
//                'partner' => 'a70fe2faafe21e4168854a176e09b1fe',
//                //此处不设置为官方接口
//                'gateway' => 'http://center.x.kcdns.net/pay_ali/gateway.do',
//                'apidomain' => 'http://center.x.kcdns.net/pay_ali/'
//            ),
//            'weixin'=>array(
//                'appid' => '1d613aa3da1d6a495c9c1dec0c801e96',
//                'mchid' => '1111',
//                'key' => '2f29b6e3abc6ebdefb55456ea6ca5dc8',
//                //此处不设置为官方接口
//                'apidomain' => 'http://center.x.kcdns.net/pay_wechat/',
//                //是否是模拟模式
//                'debug'=>true
//            ),
//        );
    }

    //初始化驱动
    private function _loadDriver($driver, $extraParams = array())
    {
        /*
         * 载入支付驱动，置入对应支付配置
         */
        $class = '\\Kcdns\\Service\\Pay\\SDK\\Driver\\' . ucfirst(strtolower($driver));
        $paymentConfig = $this->_loadConfig($driver);

        $paymentConfig['extra'] = $extraParams;

        $this->config = $paymentConfig;
        $this->payType = $driver;

        if (!class_exists($class)) {
            throw new \Exception('无效的支付驱动:' . $class);
        }
        $this->driver = new $class($paymentConfig);

        return $this->driver;
    }

    //存储生成的支付表单
    private function _savePayForm($paymentId, $payForm)
    {
        $data = array('pay_form' => is_array($payForm) ? json_encode($payForm) : $payForm);
        return M('Payment')->where(array('id' => $paymentId))->save($data);
    }

    //存储同步 异步通知响应数据
    private function _saveNotifyData($payId, $data, $type = 'notify')
    {
        //支付流水通知时间记录
        M('Payment')->where(array('id' => $payId))->save(array($type . '_time' => date('Y-m-d H:i:s')));
        //通知响应记录
        $where = array(
            'pay_id' => $payId
        );
        $save['pay_id'] = $payId;
        $save[$type == 'notify' ? 'notify_data' : 'return_data'] = serialize($data);
        $save[$type == 'notify' ? 'notify_time' : 'return_time'] = date('Y-m-d H:i:s');
        if (M('PaymentNotify')->where($where)->find()) {
            return M('PaymentNotify')->where($where)->save($save);
        } else {
            return M('PaymentNotify')->add($save);
        }
    }

    //构造回调地址
    private function _createCallbackUrl($payId, $callbackUrl)
    {
        if (strpos($callbackUrl, 'http') !== false) {
            return strpos($callbackUrl, '?') !== false ? ($callbackUrl . '&paysn=' . $payId) : ($callbackUrl . '?paysn=' . $payId);
        } else {
            return U($callbackUrl . '/paysn/' . $payId, '', true, true);
        }

    }

    //生成支付序列号
    private function _createPaySn($orderData)
    {
        //重复性保证 todo
        $data[] = microtime(true);
        $data[] = serialize($orderData);
        $data[] = mt_rand();
        return md5(implode('', $data));
    }

    //获取支付信息
    private function _getPaymentInfo($payment, $type = 'sn')
    {
        $where[$type == 'sn' ? 'pay_sn' : 'id'] = $payment;
        $payment = M('Payment')->where($where)->find();
        if (!$payment) {
            throw new \Exception('交易不存在');
        }

        if ($payment['order_info']) {
            $payment['order_info'] = unserialize($payment['order_info']);
        }

        return $payment;
    }

    //退款申请验证
    private function _checkRefund($orderNo, $amount)
    {

        $payment = M('Payment')->where(array('order_no' => $orderNo, 'status' => self::PAYMENT_STATUS_PAY, 'type' => self::PAYMENT_RECORD_TYPE_PAY))->find();

        if (!$payment) throw new \Exception('无效的支付记录');

        $where = array(
            'order_no' => $orderNo,
            'type' => self::PAYMENT_RECORD_TYPE_REFUND,
            'status' => self::PAYMENT_REFUND_STATUS_DONE
        );
        $refundAmount = M('Payment')->where($where)->sum('amount') ?: 0;

        if ($amount + $refundAmount > $payment['amount']) throw new \Exception('退款金额异常');

        return $payment;
    }

    // 获取交易流水号信息
    public function getInList($orderNo)
    {
        if ($orderNo) {
            return M('Payment')->where(['order_no' => ['in', $orderNo]])->getField('order_no,id,outer_sn');
        }
        return null;
    }


}